/*
 * SAMLProcessor
 * 
 * Version information
 * 
 * Feb 20, 2006
 * 
 * Copyright (c) AmSoft Systems, 2006
 */

package net.amsoft.iservice.isso.util;

import java.util.Calendar;

import net.amsoft.iservice.isso.client.SAMLDataObject;

import org.apache.log4j.Logger;
import org.apache.soap.util.xml.XMLParserUtils;
import org.apache.xmlbeans.XmlObject;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import x0Assertion.oasisNamesTcSAML2.AssertionType;
import x0Assertion.oasisNamesTcSAML2.AttributeStatementType;
import x0Assertion.oasisNamesTcSAML2.AttributeType;
import x0Assertion.oasisNamesTcSAML2.NameIDType;
import x0Assertion.oasisNamesTcSAML2.SubjectType;
import x0Protocol.oasisNamesTcSAML2.ArtifactDocument;
import x0Protocol.oasisNamesTcSAML2.ArtifactResolveDocument;
import x0Protocol.oasisNamesTcSAML2.ArtifactResponseDocument;
import x0Protocol.oasisNamesTcSAML2.ArtifactResponseType;
import x0Protocol.oasisNamesTcSAML2.AuthnRequestDocument;
import x0Protocol.oasisNamesTcSAML2.LogoutRequestDocument;
import x0Protocol.oasisNamesTcSAML2.LogoutResponseDocument;
import x0Protocol.oasisNamesTcSAML2.ResponseDocument;
import x0Protocol.oasisNamesTcSAML2.ResponseType;

/**
 * @author Rahul Joshi
 *  
 */
public class SAMLProcessor {

    /**
     * static members
     */
    private static Logger oLogger = Logger.getLogger(SAMLProcessor.class);

    public static final String SAML_SUCCESS_STATUS = "urn:oasis:names:tc:SAML:2.0:status:Success";
    public static final String SAML_AUTH_FAIL_STATUS = "urn:oasis:names:tc:SAML:2.0:status:AuthnFailed";
    public static final String SAML_RESPONDER_STATUS = "urn:oasis:names:tc:SAML:2.0:status:Responder";
    public static final String SAML_NO_AUTHN_CONTEXT_STATUS = "urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext";
    public static final String SAML_REQUESTER_STATUS = "urn:oasis:names:tc:SAML:2.0:status:Requester";
    public static final String SAML_REQUEST_DENIED_STATUS = "urn:oasis:names:tc:SAML:2.0:status:RequestDenied";
    public static final String SAML_REQUEST_UNSUPPORTED_STATUS = "urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported";
    public static final String SAML_UNKNOWN_PRINCIPAL_STATUS = "urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal";
    public static final String SAML_NO_AVAILABLE_IDP_STATUS = "urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP";
    private static final String SAML_VERSION_TWO_DOT_ZERO = "2.0";
    private static final String SAML_CONTACT_REPUTATION_SCORE_VAR = "ContactReputationScore";

    /**
     * function used to parse and return values from SAML Authn Request
     * 
     * @param sAuthnRequestDocumentXML
     *            Xml String of the SAML AuthnRequestDocument
     * 
     * @return SAMLDataObject <BR>
     *         following elements are set in returned SAMLDataObject object <BR>
     *         SubjectNameIDNameQualifier <BR>
     *         AssertionConsumerServiceURL <BR>
     *         ID <BR>
     *         SPXri <BR>
     *         ReputationScoreRequired <BR>
     *         null is returned if document is not successfully parsed
     */
    public static final SAMLDataObject deserializeAuthnRequest(
            String sAuthnRequestDocumentXML) {
        try {
            AuthnRequestDocument oAuthnRequestDocument = AuthnRequestDocument.Factory
                    .parse(sAuthnRequestDocumentXML);

            SAMLDataObject oSAMLDataObject = new SAMLDataObject();
            oSAMLDataObject.setSubjectNameIDNameQualifier(oAuthnRequestDocument
                    .getAuthnRequest().getSubject().getNameID()
                    .getNameQualifier());

            oSAMLDataObject
                    .setAssertionConsumerServiceURL(oAuthnRequestDocument
                            .getAuthnRequest().getAssertionConsumerServiceURL());

            oSAMLDataObject.setID(oAuthnRequestDocument.getAuthnRequest()
                    .getID());

            oSAMLDataObject.setSPXri(oAuthnRequestDocument.getAuthnRequest()
                    .getIssuer().getStringValue());

            boolean bRequestForReputationScore = (oAuthnRequestDocument
                    .getAuthnRequest().getAttributeConsumingServiceIndex() == 1)
                    ? true
                    : false;

            oSAMLDataObject
                    .setReputationScoreRequired(bRequestForReputationScore);

            String sProviderName = oAuthnRequestDocument.getAuthnRequest()
                    .getProviderName();
            if(sProviderName != null)
            {
                oSAMLDataObject.setProviderName(sProviderName);
            }
            else{
                return null;
            }
            
            oSAMLDataObject.setIsPassive(oAuthnRequestDocument
                    .getAuthnRequest().getIsPassive());
            
            oSAMLDataObject.setArAuthnContext(oAuthnRequestDocument
                    .getAuthnRequest().getRequestedAuthnContext()
                    .getAuthnContextClassRefArray());
            String sComparison;
            try {
                sComparison = oAuthnRequestDocument.getAuthnRequest()
                                        .getRequestedAuthnContext()
                                        .getComparison().toString();
            } catch (RuntimeException e) {
            	oLogger.warn("deserializeAuthnRequest() : Exception in SAMLProcessor.deserializeAuthnRequest(): " +
                        "Got null comparison type.\n Setting it to \"exact\"");
                sComparison = ISSOConstants.RAC_EXACT;
            }
            oSAMLDataObject.setComparisonType(sComparison.toString());
            
            return oSAMLDataObject;
        } catch (Exception e) {
        	oLogger.warn("deserializeAuthnRequest() : Exception : "+e.getMessage());
            return null;
        }
    }
    /**
     * function used to parse a SAML LogoutRequestDocument
     * 
     * @param sLogoutRequestDocumentXML
     *            XML String representing LogoutRequestDocument
     * @return SAMLDataObject instance <BR>
     *         <B>NameIDNameQualifer </B>containing identifer <BR>
     *         <B>ID </B>containing unique id fo the request <BR>
     *         <B>SPXri </B>containing provided id of <BR>
     *         Service Provider <BR>
     *         <B>SessionIndex </B>containing session index of the <BR>
     *         identifer at IDP <BR>
     *         <B>Destination </B>containing the endpoint where request was sent
     *         <BR>
     *         null is returned if document is not successfully parsed
     */
    public static final SAMLDataObject deserializeLogoutRequest(
            String sLogoutRequestDocumentXML) {
        try {
            LogoutRequestDocument oLogoutRequestDocument = LogoutRequestDocument.Factory
                    .parse(sLogoutRequestDocumentXML);

            SAMLDataObject oSAMLDataObject = new SAMLDataObject();

            oSAMLDataObject
                    .setSubjectNameIDNameQualifier(oLogoutRequestDocument
                            .getLogoutRequest().getNameID().getNameQualifier());

            oSAMLDataObject.setID(oLogoutRequestDocument.getLogoutRequest()
                    .getID());

            oSAMLDataObject.setSPXri(oLogoutRequestDocument.getLogoutRequest()
                    .getIssuer().getStringValue());

            oSAMLDataObject.setSessionIndex(oLogoutRequestDocument
                    .getLogoutRequest().getSessionIndexArray(0));

            oSAMLDataObject.setDestination(oLogoutRequestDocument
                    .getLogoutRequest().getDestination());

            return oSAMLDataObject;
        } catch (Exception e) {
        	oLogger.warn("deserializeLogoutRequest() : Exception : "+e.getMessage());
            return null;
        }
    }

    /**
     * Creates and returns negative SAML Authentication Response
     * 
     * @param destination
     *            Destinatino URL where response will be sent
     * @param inResponseTo
     *            ID of the Authentication Request in response to which <BR>
     *            this SAML Document is being generated
     * @param providerID
     *            providerID of the ISSO Service
     * @param status
     *            SAML Status
     * @return XML representing ResponseDocument
     */
    public static String serializeNegativeAuthnResponse(String destination,
            String inResponseTo, String providerID, String uniqueID, String status) {
        ResponseDocument samlResponseDocument = null;

        try {
            samlResponseDocument = ResponseDocument.Factory.newInstance();

            ResponseType samlResponseType = samlResponseDocument
                    .addNewResponse();

            NameIDType issuer = NameIDType.Factory.newInstance();
            issuer.setNameQualifier(providerID);
            samlResponseType.setID(uniqueID);
            samlResponseType.setVersion(SAML_VERSION_TWO_DOT_ZERO);
            samlResponseType.setIssueInstant(Calendar.getInstance());
            samlResponseType.setIssuer(issuer);

            samlResponseType.addNewStatus().addNewStatusCode().setValue(
                    status);

            samlResponseType.setDestination(destination);
            samlResponseType.setInResponseTo(inResponseTo);

        } catch (Exception e) {
        	oLogger.warn("serializeNegativeAuthnResponse() : Exception : "+e.getMessage());            
        }
        if (samlResponseDocument != null) {
            return samlResponseDocument.xmlText();
        } else {
            return null;
        }
    }//Method : createNegativeSAMLResponse

    /**
     * Creates and returns positive SAML Authentication Response
     * 
     * @param destination
     *            Destinatino URL where response will be sent
     * @param inResponseTo
     *            ID of the Authentication Request in response to which <BR>
     *            this SAML Document is being generated
     * @param providerID
     *            providerID of the ISSO Service
     * @param nameQualifier
     *            Subject to which Authentication request has come
     * @param sessionIndex
     *            session index at the IDP
     * @param includeReputationScore
     *            boolean flag, if true then reputation score is also added <BR>
     *            in SAML Response Document otherwise not
     * @param reputationScore
     *            reputation score that will be added in SAML Response Document,
     *            <BR>
     *            required only if <code>includeReputationScore</code> is true
     * 
     * @return XML String representing SAML ResponseDocument
     */
    public static String serializePositiveAuthnLResponse(String destination,
            String inResponseTo, String providerID, String nameQualifier,
            String sessionIndex, boolean includeReputationScore,
            int reputationScore, String uniqueResponseID,
            String uniqueAssertionID) {
        String requestId = null;
        ResponseDocument samlResponseDocument = null;
        SubjectType samlSubjectType = null;
        AssertionType samlAssertionType = null;
        try {
            samlResponseDocument = ResponseDocument.Factory.newInstance();

            ResponseType samlResponseType = samlResponseDocument
                    .addNewResponse();

            NameIDType issuer = NameIDType.Factory.newInstance();
            issuer.setNameQualifier(providerID);
            samlResponseType.setID(uniqueResponseID);
            samlResponseType.setVersion(SAML_VERSION_TWO_DOT_ZERO);
            samlResponseType.setIssueInstant(Calendar.getInstance());

            samlResponseType.addNewStatus().addNewStatusCode().setValue(
                    SAML_SUCCESS_STATUS);

            samlResponseType.setDestination(destination);
            samlResponseType.setIssuer(issuer);
            samlResponseType.setInResponseTo(inResponseTo);
            
            samlAssertionType = samlResponseType.addNewAssertion();
            samlAssertionType.setID(uniqueAssertionID);
            samlAssertionType.setVersion(SAML_VERSION_TWO_DOT_ZERO);
            samlAssertionType.setIssueInstant(Calendar.getInstance());
            samlAssertionType.setIssuer(issuer);
            if (includeReputationScore) {
                /**
                 * Set AttributStatement in Assertion
                 */
                AttributeStatementType ast = samlAssertionType
                        .addNewAttributeStatement();

                AttributeType at = ast.addNewAttribute();
                at.setName(SAML_CONTACT_REPUTATION_SCORE_VAR);
                at.addNewAttributeValue();
                Document doc = XMLParserUtils.getXMLDocBuilder().newDocument();
                Element elem = doc.createElement("AttributeValue");

                Node node = doc.createTextNode(new Integer(reputationScore)
                        .toString());

                elem.appendChild(node);
                XmlObject xObj = XmlObject.Factory.parse((Node) node);
                at.setAttributeValueArray(0, xObj);
            } else {
                oLogger.info("serializePositiveAuthnLResponse() : "
                        + "Contact Reputation Score Not Requested");
            }
            samlAssertionType.addNewAuthnStatement().setSessionIndex(
                    sessionIndex);

            samlSubjectType = samlAssertionType.addNewSubject();
            samlSubjectType.addNewNameID().setNameQualifier(nameQualifier);

            samlAssertionType.addNewSubject().addNewSubjectConfirmation()
                    .addNewSubjectConfirmationData().setInResponseTo(requestId);

        } catch (Exception e) {
        	oLogger.warn("serializePositiveAuthnLResponse() : Exception : "+e.getMessage());            
        }
        if (samlResponseDocument != null) {
            return samlResponseDocument.xmlText();
        } else {
            return null;
        }
    }//Method : createPositiveSAMLResponse

    /**
     * Creates and returns SAMLArtifact containing Artifact Token
     * 
     * @param message
     *            message to be included in SAMLArtifact document
     * @return xml representing ArtifactDocument
     */
    public static String serializeArtifactDocument(String message) {
        ArtifactDocument artifactDocument = ArtifactDocument.Factory
                .newInstance();

        try {
            artifactDocument.setArtifact(message);
            return artifactDocument.xmlText();
        } catch (Exception e) {
        	oLogger.warn("serializeArtifactDocument() : Exception : "+e.getMessage());
            return null;
        }
    }//method: createSAMLArtifact

    /**
     * function used to create a SAML Logout Response Document
     * 
     * @param inResponseTo
     * @param destination
     * @param status
     *            <BR>
     *            0 for success status <BR>
     *            1 for Responder status <BR>
     *            2 and others for NoAuthnContext status <BR>
     * @param providerID
     * @return xml string representing LogoutResponseDocument
     */
    public static String serializeLogoutResponse(String inResponseTo,
            String destination, int status, String providerID, String uniqueID) {
        LogoutResponseDocument samlResponse = null;
        try {
            samlResponse = LogoutResponseDocument.Factory.newInstance();
            samlResponse.addNewLogoutResponse();
            NameIDType issuer = NameIDType.Factory.newInstance();
            issuer.setNameQualifier(providerID);
            samlResponse.getLogoutResponse().setIssuer(issuer);

            samlResponse.getLogoutResponse().setIssueInstant(
                    Calendar.getInstance());

            samlResponse.getLogoutResponse().setInResponseTo(inResponseTo);
            samlResponse.getLogoutResponse().setDestination(destination);

            samlResponse.getLogoutResponse().setVersion(
                    SAML_VERSION_TWO_DOT_ZERO);

            switch (status) {
                case 0 :
                    /*
                     * User confirmed to do a logout
                     */
                    samlResponse.getLogoutResponse().addNewStatus()
                            .addNewStatusCode().setValue(SAML_SUCCESS_STATUS);
                    break;
                case 1 :
                    /*
                     * User declined to do a logout
                     */
                    samlResponse.getLogoutResponse().addNewStatus()
                            .addNewStatusCode().setValue(SAML_RESPONDER_STATUS);
                    break;
                case 2 :
                default :
                    /*
                     * Invalid authn credentials in logout request
                     */
                    samlResponse.getLogoutResponse().addNewStatus()
                            .addNewStatusCode().setValue(
                                    SAML_NO_AUTHN_CONTEXT_STATUS);
                    break;
            }//switch ends
            samlResponse.getLogoutResponse().setID(uniqueID);
        } catch (Exception e) {
        	oLogger.warn("serializeLogoutResponse() : Exception : "+e.getMessage());            
        }
        if (samlResponse != null) {
            return samlResponse.xmlText();
        } else {
            return null;
        }
    }//createLogoutResponseDocument

    /**
     * function used to check if an authentication request also inculde <BR>
     * request for reputation score <BR>
     * This is done my looking at Attribute Consuming Service Index in <BR>
     * request received
     * 
     * @param authRequestXML
     *            XML String representing SAML AuthnRequestDocument
     * @return boolean value, <B>true </B> if there is a request for <BR>
     *         reputation score, else <B>false </B>
     */
    public static boolean checkContactReputaionScoreRequest(
            String authRequestXML) {
        boolean isRequested = false;
        try {
            AuthnRequestDocument samlRequest = AuthnRequestDocument.Factory
                    .parse(authRequestXML);

            if (samlRequest.getAuthnRequest()
                    .getAttributeConsumingServiceIndex() == 1) {
                /*
                 * Contact Reputation Score Requested
                 */
                isRequested = true;
            } else {
                isRequested = false;
            }
        } catch (Exception e) {
        	oLogger.warn("checkContactReputaionScoreRequest() : Exception : "+e.getMessage());            
        }
        return isRequested;
    }//checkContactReputaionScoreRequest() ends

    /**
     * 
     * @param artResolveDocXML
     *            xml representing ArtifactResolveDocument
     * @param nameQualifier
     *            NameQualifier of the issuer
     * @param storedResponse
     *            stored response document
     * 
     * @return xml text representing ArtifactResponseDocument
     */
    public static String serializeArtifactResponseDocument(
            String artResolveDocXML, String nameQualifier, String storedResponse) {
        ArtifactResponseDocument artRespDoc = ArtifactResponseDocument.Factory
                .newInstance();
        try {
            ArtifactResolveDocument artResolvDoc = ArtifactResolveDocument.Factory
                    .parse(artResolveDocXML);

            ArtifactResponseType artifactResponseType = null;
            NameIDType issuer = null;
            artRespDoc = ArtifactResponseDocument.Factory.newInstance();
            artifactResponseType = artRespDoc.addNewArtifactResponse();
            if (storedResponse != null) {
                artifactResponseType.addNewExtensions().set(
                        XmlObject.Factory.parse(storedResponse));
            } else {
                oLogger.info("serializeArtifactResponseDocument() : Response Document to be returned "
                                + "in ArtifactResponseDocument is null!\nThis is OK, "
                                + "and will return an ArtifactResponseDocument Containing no "
                                + "response");
            }
            artifactResponseType.setIssueInstant(Calendar.getInstance());
            issuer = NameIDType.Factory.newInstance();
            issuer.setNameQualifier(nameQualifier);
            artifactResponseType.setIssuer(issuer);

            artifactResponseType.addNewStatus().addNewStatusCode().setValue(
                    SAML_SUCCESS_STATUS);

            artifactResponseType.setInResponseTo(artResolvDoc
                    .getArtifactResolve().getID());

        } catch (Exception e) {
        	oLogger.warn("serializeArtifactResponseDocument() : Exception : "+e.getMessage());            
        }
        if (artRespDoc != null) {
            return artRespDoc.xmlText();
        } else {
            return null;
        }
    }//Method : createArtifactResponseDocument

    /**
     * returns the ArtifactResolveDocument received in a SOAP Envelope
     * 
     * @param document
     *            document from which SOAP envelope will be created
     * @return XmlObject representing ArtifactResolveDocument
     */
    public static final XmlObject getArtifactResolveDocument(Document document) {
        try {
            return SOAPProcessor.getValueFromSOAPEnvelope(document);
        } catch (Exception e) {
        	oLogger.warn("getArtifactResolveDocument() : Exception : "+e.getMessage());
            return null;
        }
    }

    /**
     * function used to get the artifact from ArtifactResolveDocument <BR>
     * 
     * @param oxmlArtifactResolveDocument
     *            XmlObject representing ArtifactResolveDocument
     * @return Artifact recieved in the ArtifactResolveDocument
     */
    public static final String deserializeArtifactResolveDocument(
            XmlObject oxmlArtifactResolveDocument) {
        try {
            ArtifactResolveDocument oArtifactResolveDocument = ArtifactResolveDocument.Factory
                    .parse(oxmlArtifactResolveDocument.xmlText());

            return oArtifactResolveDocument.getArtifactResolve().getArtifact();
        } catch (Exception e) {
        	oLogger.warn("deserializeArtifactResolveDocument() : Exception : "+e.getMessage());
            return null;
        }
    }//getArtifact ends

}//SAMLProcessor
